package com.hqd.ch03.v50.context.annotation;

import com.hqd.ch03.utils.ClassUtils;
import com.hqd.ch03.v50.aop.framework.autoproxy.AutoProxyUtils;
import com.hqd.ch03.v50.aware.BeanClassLoaderAware;
import com.hqd.ch03.v50.aware.ResourceLoaderAware;
import com.hqd.ch03.v50.beans.PropertyValues;
import com.hqd.ch03.v50.beans.factory.BeanDefinitionStoreException;
import com.hqd.ch03.v50.beans.factory.config.BeanDefinition;
import com.hqd.ch03.v50.beans.factory.config.ConfigurableListableBeanFactory;
import com.hqd.ch03.v50.beans.factory.config.InstantiationAwareBeanPostProcessor;
import com.hqd.ch03.v50.beans.factory.parsing.PassThroughSourceExtractor;
import com.hqd.ch03.v50.beans.factory.parsing.SourceExtractor;
import com.hqd.ch03.v50.beans.factory.support.AbstractBeanDefinition;
import com.hqd.ch03.v50.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import com.hqd.ch03.v50.context.EnvironmentAware;
import com.hqd.ch03.v50.core.NativeDetector;
import com.hqd.ch03.v50.core.Ordered;
import com.hqd.ch03.v50.core.PriorityOrdered;
import com.hqd.ch03.v50.core.env.Environment;
import com.hqd.ch03.v50.core.env.StandardEnvironment;
import com.hqd.ch03.v50.core.type.AnnotationMetadata;
import com.hqd.ch03.v50.core.type.MethodMetadata;
import com.hqd.ch03.v50.core.type.classreading.CachingMetadataReaderFactory;
import com.hqd.ch03.v50.core.type.classreading.MetadataReaderFactory;
import com.hqd.ch03.v50.factory.BeanFactory;
import com.hqd.ch03.v50.factory.annotation.AnnotatedBeanDefinition;
import com.hqd.ch03.v50.factory.config.BeanDefinitionHolder;
import com.hqd.ch03.v50.factory.support.BeanNameGenerator;
import com.hqd.ch03.v50.io.ResourceLoader;
import com.hqd.ch03.v50.io.support.DefaultResourceLoader;
import com.hqd.ch03.v50.registry.BeanDefinitionRegistry;
import com.hqd.ch03.v50.registry.SingletonBeanRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
        PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

    public static final AnnotationBeanNameGenerator IMPORT_BEAN_NAME_GENERATOR =
            FullyQualifiedAnnotationBeanNameGenerator.INSTANCE;

    private static final String IMPORT_REGISTRY_BEAN_NAME =
            ConfigurationClassPostProcessor.class.getName() + ".importRegistry";


    private final Log logger = LogFactory.getLog(getClass());
    private final Set<Integer> registriesPostProcessed = new HashSet<>();
    private final Set<Integer> factoriesPostProcessed = new HashSet<>();
    private SourceExtractor sourceExtractor = new PassThroughSourceExtractor();

    private Environment environment;
    private ResourceLoader resourceLoader = new DefaultResourceLoader();

    private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
    private boolean setMetadataReaderFactoryCalled = false;

    private ConfigurationClassBeanDefinitionReader reader;

    private boolean localBeanNameGeneratorSet = false;

    private BeanNameGenerator componentScanBeanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;

    private BeanNameGenerator importBeanNameGenerator = IMPORT_BEAN_NAME_GENERATOR;

    /**
     * 获取所有配置类
     *
     * @param registry
     * @return
     */
    private List<BeanDefinitionHolder> findAllConfigurations(BeanDefinitionRegistry registry, String[] candidateNames) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();

        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            //包含Configuration注解
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
        return configCandidates;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;  // within PriorityOrdered
    }

    public void setSourceExtractor(SourceExtractor sourceExtractor) {
        this.sourceExtractor = (sourceExtractor != null ? sourceExtractor : new PassThroughSourceExtractor());
    }

    public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) {
        this.metadataReaderFactory = metadataReaderFactory;
        this.setMetadataReaderFactoryCalled = true;
    }

    public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
        this.localBeanNameGeneratorSet = true;
        this.componentScanBeanNameGenerator = beanNameGenerator;
        this.importBeanNameGenerator = beanNameGenerator;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
        if (!this.setMetadataReaderFactoryCalled) {
            this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
        }
    }

    @Override
    public void setBeanClassLoader(ClassLoader beanClassLoader) {
        this.beanClassLoader = beanClassLoader;
        if (!this.setMetadataReaderFactoryCalled) {
            this.metadataReaderFactory = new CachingMetadataReaderFactory(beanClassLoader);
        }
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);

        processConfigBeanDefinitions(registry);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        int factoryId = System.identityHashCode(beanFactory);
        if (this.factoriesPostProcessed.contains(factoryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + beanFactory);
        }
        this.factoriesPostProcessed.add(factoryId);
        if (!this.registriesPostProcessed.contains(factoryId)) {
            processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
        }

        enhanceConfigurationClasses(beanFactory);
        beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    }

    /**
     * 开始处理 @Configuration 注解
     *
     * @param registry
     */
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        //获取所有的bd
        String[] candidateNames = registry.getBeanDefinitionNames();
        List<BeanDefinitionHolder> configCandidates = findAllConfigurations(registry, candidateNames);

        //没有Configuration注解则返回
        if (configCandidates.isEmpty()) {
            return;
        }
        /**
         * 进行排序
         */
        sortConfiguration(configCandidates);
        SingletonBeanRegistry sbr = initRegistryAndEnv(registry);

        /**
         * 解析Configuration注解
         */
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, null, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);

        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        do {
            parser.parse(candidates);
            /**
             * 符合条件的Configuration
             */
            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);

            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            /**
             * 将Configuration声明的类转成BD
             */
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);
            //    processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

            candidates.clear();
            /**
             * 如果BD有所增加
             */
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                for (String candidateName : newCandidateNames) {
                    //将新增的对象添加到集合中
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        }
        while (!candidates.isEmpty());

        // 将ImportRegistry注册为bean以支持ImportAware @Configuration类
        if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
            sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
        }

        if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
            ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
        }
    }

    /**
     * 配置类排序
     *
     * @param configCandidates
     */
    private void sortConfiguration(List<BeanDefinitionHolder> configCandidates) {
        configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });
    }

    /**
     * 初始化环境和仓库
     *
     * @param registry
     * @return
     */
    private SingletonBeanRegistry initRegistryAndEnv(BeanDefinitionRegistry registry) {
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet) {//是否使用系统生成beanName
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
                        AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
                if (generator != null) {
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
        }

        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }
        return sbr;
    }

    public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
        Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
            AnnotationMetadata annotationMetadata = null;
            MethodMetadata methodMetadata = null;
            if (beanDef instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDef;
                annotationMetadata = annotatedBeanDefinition.getMetadata();
                methodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata();
            }
            if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
                AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
                if (!abd.hasBeanClass()) {
                    boolean liteConfigurationCandidateWithoutBeanMethods =
                            (ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) &&
                                    annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));
                    if (!liteConfigurationCandidateWithoutBeanMethods) {
                        try {
                            abd.resolveBeanClass(this.beanClassLoader);
                        } catch (Throwable ex) {
                            throw new IllegalStateException(
                                    "Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
                        }
                    }
                }
            }
            if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
                if (!(beanDef instanceof AbstractBeanDefinition)) {
                    throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                            beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
                } else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
                    logger.info("Cannot enhance @Configuration bean definition '" + beanName +
                            "' since its singleton instance has been created too early. The typical cause " +
                            "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                            "return type: Consider declaring such methods as 'static'.");
                }
                configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
            }
        }
        if (configBeanDefs.isEmpty() || NativeDetector.inNativeImage()) {
            return;
        }

        ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
        for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
            AbstractBeanDefinition beanDef = entry.getValue();
            // If a @Configuration class gets proxied, always proxy the target class
            beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
            // Set enhanced subclass of the user-specified bean class
            Class<?> configClass = beanDef.getBeanClass();
            Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
            if (configClass != enhancedClass) {
                if (logger.isTraceEnabled()) {
                    logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
                            "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
                }
                beanDef.setBeanClass(enhancedClass);
            }
        }
        //  enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();
    }


    private static class ImportAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

        private final BeanFactory beanFactory;

        public ImportAwareBeanPostProcessor(BeanFactory beanFactory) {
            this.beanFactory = beanFactory;
        }

        @Override
        public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
            if (bean instanceof ConfigurationClassEnhancer.EnhancedConfiguration) {
                ((ConfigurationClassEnhancer.EnhancedConfiguration) bean).setBeanFactory(this.beanFactory);
            }
            return pvs;
        }

        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            if (bean instanceof ImportAware) {
                ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
                AnnotationMetadata importingClass = ir.getImportingClassFor(ClassUtils.getUserClass(bean).getName());
                if (importingClass != null) {
                    ((ImportAware) bean).setImportMetadata(importingClass);
                }
            }
            return bean;
        }
    }

}

